﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
//using System.Windows;
using System.Windows.Media;
using AIStudio.Wpf.DiagramDesigner.Geometrys;
using AIStudio.Wpf.DiagramDesigner.Helpers;

namespace AIStudio.Wpf.DiagramDesigner
{
    public class ConnectorViewModel : SelectableDesignerItemViewModelBase
    {
        public ConnectorViewModel(IDiagramViewModel parent, FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, DrawMode drawMode, RouterMode routerMode)
        {
            Parent = parent;
            PathMode = drawMode.ToString();
            RouterMode = routerMode.ToString();
            Init(sourceConnectorInfo, sinkConnectorInfo);
        }

        public ConnectorViewModel(IDiagramViewModel parent, FullyCreatedConnectorInfo sourceConnectorInfo, FullyCreatedConnectorInfo sinkConnectorInfo, ConnectionItem designer) : base(parent, designer)
        {
            PathMode = designer.PathMode;
            RouterMode = designer.RouterMode;
            Init(sourceConnectorInfo, sinkConnectorInfo);
        }

        public ConnectorViewModel(FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo, DrawMode drawMode, RouterMode routerMode) : this(null, sourceConnectorInfo, sinkConnectorInfo, drawMode, routerMode)
        {

        }

        private void Init(FullyCreatedConnectorInfo sourceConnectorInfo, ConnectorInfoBase sinkConnectorInfo)
        {
            this.Parent = sourceConnectorInfo.DataItem.Parent;
            if (Parent != null && Parent.ColorViewModel != null)
            {
                this.ColorViewModel = CopyHelper.Mapper(Parent.ColorViewModel);
            }
            if (sinkConnectorInfo is FullyCreatedConnectorInfo sink && sink.DataItem.ShowArrow == false)
            {
                this.ColorViewModel.RightArrowPathStyle = ArrowPathStyle.None;
            }

            var routetype = GlobalType.AllTypes.Where(p => typeof(IRouter).IsAssignableFrom(p)).FirstOrDefault(p => p.Name == RouterMode);
            Router = routetype != null ? (System.Activator.CreateInstance(routetype) as IRouter) : new RouterNormal();

            var pathGeneratortype = GlobalType.AllTypes.Where(p => typeof(IPathGenerator).IsAssignableFrom(p)).FirstOrDefault(p => p.Name == PathMode);
            PathGenerator = pathGeneratortype != null ? (System.Activator.CreateInstance(pathGeneratortype) as IPathGenerator) : new ConnectingLineSmooth();

            this.SourceConnectorInfo = sourceConnectorInfo;
            this.SinkConnectorInfo = sinkConnectorInfo;
            DeleteConnectionCommand = new SimpleCommand(DeleteConnection);
            AddVertexCommand = new SimpleCommand(AddVertex);       
        }

        public override SelectableDesignerItemBase ToXmlObject()
        {
            if (IsFullConnection)
            {
                ConnectionItem connection = new ConnectionItem(this);

                return connection;
            }
            else
            {
                return null;
            }
        }

        public override Type ToXmlType()
        {
            return typeof(ConnectionItem);
        }

        private PointBase _sourceA;
        public PointBase SourceA
        {
            get
            {
                return _sourceA;
            }
            set
            {
                if (SetProperty(ref _sourceA, value))
                {
                    UpdateArea();
                }
            }
        }

        private PointBase _sourceB;
        public PointBase SourceB
        {
            get
            {
                return _sourceB;
            }
            set
            {
                if (SetProperty(ref _sourceB, value))
                {
                    UpdateArea();
                }
            }
        }

        private ObservableCollection<ConnectorPoint> _connectionPoints = new ObservableCollection<ConnectorPoint>();
        public ObservableCollection<ConnectorPoint> ConnectionPoints
        {
            get
            {
                return _connectionPoints;
            }
            private set
            {
                if (_connectionPoints != null)
                {
                    foreach (var connectionPoint in _connectionPoints)
                    {
                        connectionPoint.PropertyChanged -= new WeakINPCEventHandler(ConnectionPoint_PropertyChanged).Handler;
                    }
                }
                SetProperty(ref _connectionPoints, value);
                if (_connectionPoints != null)
                {
                    foreach (var connectionPoint in _connectionPoints)
                    {
                        connectionPoint.PropertyChanged += new WeakINPCEventHandler(ConnectionPoint_PropertyChanged).Handler;
                    }
                }
            }
        }

        private PointBase _startPoint;
        public PointBase StartPoint
        {
            get
            {
                return _startPoint;
            }
            private set
            {
                SetProperty(ref _startPoint, value);
            }
        }

        private PointBase _endPoint;
        public PointBase EndPoint
        {
            get
            {
                return _endPoint;
            }
            private set
            {
                SetProperty(ref _endPoint, value);
            }
        }

        private double _startAngle;
        public double StartAngle
        {
            get
            {
                return _startAngle;
            }
            private set
            {
                SetProperty(ref _startAngle, value);
            }
        }

        private double _endAngle;
        public double EndAngle
        {
            get
            {
                return _endAngle;
            }
            private set
            {
                SetProperty(ref _endAngle, value);
            }
        }

        private RectangleBase _area;
        public RectangleBase Area
        {
            get
            {
                return _area;
            }
            private set
            {
                RectangleBase oldarea = _area;
                if (SetProperty(ref _area, value))
                {
                    UpdateConnectionPoints();
                    OutTextItemLocation(oldarea, value);
                }
            }
        }

        public string PathMode
        {
            get; set;
        }

        public string RouterMode
        {
            get; set;
        }

        public IRouter Router
        {
            get; set;
        }

        public IPathGenerator PathGenerator
        {
            get; set;
        }

        private PathGeneratorResult _pathGeneratorResult;
        public PathGeneratorResult PathGeneratorResult
        {
            get
            {
                return _pathGeneratorResult;
            }
            private set
            {
                SetProperty(ref _pathGeneratorResult, value);
            }
        }

        private bool _shouldInsertAnchor;
        public bool ShouldInsertAnchor
        {
            get
            {
                return _shouldInsertAnchor;
            }
            set
            {
                SetProperty(ref _shouldInsertAnchor, value);
            }
        }

        //待完善这两处
        public List<LinkVertexModel> Vertices { get; } = new List<LinkVertexModel>();
        public List<LinkLabelModel> Labels { get; set; } = new List<LinkLabelModel>();

        public virtual Dictionary<string, string> PropertiesSetting
        {
            get
            {
                return new Dictionary<string, string>()
                {
                    { "Text","文本" },
                };
            }
        }

        private FullyCreatedConnectorInfo _sourceConnectorInfo;
        public FullyCreatedConnectorInfo SourceConnectorInfo
        {
            get
            {
                return _sourceConnectorInfo;
            }
            set
            {
                if (SetProperty(ref _sourceConnectorInfo, value))
                {
                    SourceA = PointHelper.GetPointForConnector(_sourceConnectorInfo);
                    (_sourceConnectorInfo.DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
                }
            }
        }

        private ConnectorInfoBase _sinkConnectorInfo;
        public ConnectorInfoBase SinkConnectorInfo
        {
            get
            {
                return _sinkConnectorInfo;
            }
            set
            {
                if (SetProperty(ref _sinkConnectorInfo, value))
                {
                    SourceB = _sinkConnectorInfo.Position;
                    if (_sinkConnectorInfo is FullyCreatedConnectorInfo)
                    {
                        (((FullyCreatedConnectorInfo)_sinkConnectorInfo).DataItem as INotifyPropertyChanged).PropertyChanged += new WeakINPCEventHandler(ConnectorViewModel_PropertyChanged).Handler;
                    }
                }
            }
        }

        public FullyCreatedConnectorInfo SinkConnectorInfoFully
        {
            get
            {
                return SinkConnectorInfo as FullyCreatedConnectorInfo;
            }
        }

        public PartCreatedConnectionInfo SinkConnectorInfoPart
        {
            get
            {
                return SinkConnectorInfo as PartCreatedConnectionInfo;
            }
        }

        public PointBase OnGoingPosition
        {
            get
            {
                return SinkConnectorInfoPart?.MiddlePosition ?? PointBase.Zero;
            }
        }

        public bool IsFullConnection
        {
            get
            {
                return SinkConnectorInfoFully != null;
            }
        }

        public bool IsPortless => SourceConnectorInfo?.DataItem?.Connectors?.Count() == 0;

        private void UpdateArea()
        {
            Area = new RectangleBase(SourceA, SourceB);
        }

        private void UpdateConnectionPoints()
        {
            if (SourceConnectorInfo == null || SinkConnectorInfo == null)
                return;

            var route = Router.Get(Parent, this);

            (var source, var target) = FindConnectionPoints(route);
            if (source == null || target == null)               
                return;         

            PathGeneratorResult = PathGenerator.Get(Parent, this, route, source.Value, target.Value);
            
            StartPoint = new PointBase(source.Value.X - Area.Left, source.Value.Y - Area.Top);
            EndPoint = new PointBase(target.Value.X - Area.Left, target.Value.Y - Area.Top);
            StartAngle = PathGeneratorResult.SourceMarkerAngle;
            EndAngle = PathGeneratorResult.TargetMarkerAngle;
        }

        private void ConnectorViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "ItemHeight":
                case "ItemWidth":
                case "Left":
                case "Top":
                    SourceA = PointHelper.GetPointForConnector(this.SourceConnectorInfo);
                    if (IsFullConnection)
                    {
                        SourceB = PointHelper.GetPointForConnector(this.SinkConnectorInfoFully);
                    }
                    break;

            }
        }

        private void ConnectionPoint_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "Left":
                case "Top":
                    RaisePropertyChanged(nameof(ConnectionPoints));
                    break;

            }
        }

        public SimpleCommand DeleteConnectionCommand
        {
            get; set;
        }

        public SimpleCommand AddVertexCommand
        {
            get; set;
        }

        private void DeleteConnection(object args)
        {
            if (this.Parent is IDiagramViewModel)
            {
                var diagramVM = this.Parent as IDiagramViewModel;
                diagramVM.RemoveItemCommand.Execute(this);
            }
        }

        private void AddVertex(object parameter)
        {
            MouseButtonEventArgs mosueArg = ((EventToCommandArgs)parameter).EventArgs as MouseButtonEventArgs;
            var position = mosueArg.GetPosition(((EventToCommandArgs)parameter).Sender as IInputElement);
     
            ConnectionPoints.Add(new ConnectorPoint(position.X, position.Y));

            if (!((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control))
            {
                ShouldInsertAnchor = false;
            }
        }

        protected override void ExecuteEditCommand(object param)
        {
            if (this.OutTextItem != null) return;
            AddText("");
        }

        public void AddText(string text)
        {
            if (this.Parent is IDiagramViewModel)
            {
                var diagramVM = this.Parent as IDiagramViewModel;

                TextDesignerItemViewModel textitem = new TextDesignerItemViewModel();
                textitem.ItemWidth = Double.NaN;
                textitem.ItemHeight = double.NaN;
                //if (this.PathMode == DrawMode.ConnectingLineBoundary.ToString())
                //{
                //    var mid = (int)(ConnectionPoints.Count / 2);
                //    var p = PathGenerators.SegmentMiddlePoint(ConnectionPoints[mid - 1], ConnectionPoints[mid]);
                //    textitem.Left = this.Area.Left + p.X + 2;
                //    textitem.Top = this.Area.Top + p.Y - 15;
                //}
                //else
                {
                    textitem.Left = this.Area.Left + this.Area.Width / 2 - 16;
                    textitem.Top = this.Area.Top + this.Area.Height / 2 - 5;
                }
                textitem.Watermark = null;
                textitem.ZIndex = diagramVM.Items.Count;
                textitem.ParentId = this.Id;
                textitem.ParentItem = this;
                textitem.ColorViewModel.FillColor = new ColorObject() { Color = Colors.White };
                textitem.Text = text;

                diagramVM.DirectAddItemCommand.Execute(textitem);

                this.OutTextItem = textitem;
            }
        }

        public void OutTextItemLocation(RectangleBase oldArea, RectangleBase newArea)
        {
            if (this.OutTextItem is TextDesignerItemViewModel text)
            {
                var oldpoint = new PointBase(oldArea.Left + oldArea.Width / 2, oldArea.Top + oldArea.Height / 2);
                var newpoint = new PointBase(newArea.Left + newArea.Width / 2, newArea.Top + newArea.Height / 2);

                text.Left = text.Left + newpoint.X - oldpoint.X;
                text.Top = text.Top + newpoint.Y - oldpoint.Y;
            }
        }

        private (PointBase? source, PointBase? target) FindConnectionPoints(PointBase[] route)
        {
            if (IsPortless) // Portless
            {
                if (SourceConnectorInfo.DataItem == null || (IsFullConnection && SinkConnectorInfoFully.DataItem == null))
                    return (null, null);

                var sourceCenter = SourceConnectorInfo.DataItem.GetBounds().Center;
                var targetCenter = SinkConnectorInfoFully?.DataItem?.GetBounds().Center ?? OnGoingPosition;
                var firstPt = route.Length > 0 ? route[0] : targetCenter;
                var secondPt = route.Length > 0 ? route[0] : sourceCenter;
                var sourceLine = new LineBase(firstPt, sourceCenter);
                var targetLine = new LineBase(secondPt, targetCenter);
                var sourceIntersections = SourceConnectorInfo.DataItem.GetShape().GetIntersectionsWithLine(sourceLine);
                var targetIntersections = SinkConnectorInfoFully.DataItem.GetShape()?.GetIntersectionsWithLine(targetLine) ?? new PointBase[] { OnGoingPosition };
                var sourceIntersection = GetClosestPointTo(sourceIntersections, firstPt);
                var targetIntersection = GetClosestPointTo(targetIntersections, secondPt);
                return (sourceIntersection ?? sourceCenter, targetIntersection ?? targetCenter);
            }
            else
            {
                var source = SourceConnectorInfo.MiddlePosition;//GetPortPositionBasedOnAlignment(SourceConnectorInfo, ColorViewModel.LeftArrowSizeStyle);
                var target = SinkConnectorInfo.MiddlePosition;// GetPortPositionBasedOnAlignment(SinkConnectorInfoFully, ColorViewModel.RightArrowSizeStyle);
                return (source, target);
            }
        }

        private PointBase? GetPortPositionBasedOnAlignment(ConnectorInfoBase port, ArrowSizeStyle marker)
        {
            if (port == null)
                return null;

            if (marker == null)
                return port.MiddlePosition;

            var pt = port.Position;
            switch (port.Orientation)
            {
                case ConnectorOrientation.Top:
                    return new PointBase(pt.X + port.ConnectorWidth / 2, pt.Y);
                case ConnectorOrientation.TopRight:
                    return new PointBase(pt.X + port.ConnectorWidth, pt.Y);
                case ConnectorOrientation.Right:
                    return new PointBase(pt.X + port.ConnectorWidth, pt.Y + port.ConnectorHeight / 2);
                case ConnectorOrientation.BottomRight:
                    return new PointBase(pt.X + port.ConnectorWidth, pt.Y + port.ConnectorHeight);
                case ConnectorOrientation.Bottom:
                    return new PointBase(pt.X + port.ConnectorWidth / 2, pt.Y + port.ConnectorHeight);
                case ConnectorOrientation.BottomLeft:
                    return new PointBase(pt.X, pt.Y + port.ConnectorHeight);
                case ConnectorOrientation.Left:
                    return new PointBase(pt.X, pt.Y + port.ConnectorHeight / 2);
                default:
                    return pt;
            }
        }

        private PointBase? GetClosestPointTo(IEnumerable<PointBase> points, PointBase point)
        {
            var minDist = double.MaxValue;
            PointBase? minPoint = null;

            foreach (var pt in points)
            {
                var dist = pt.DistanceTo(point);
                if (dist < minDist)
                {
                    minDist = dist;
                    minPoint = pt;
                }
            }

            return minPoint;
        }



    }
}
